/*
 * hd.c
 *
 *  Created on: 2016-11-11
 *      Author: m
 */
#include "hd.h"
#include "proc.h"
#include "type.h"
#include "i8259.h"
#include "task_sys.h"
#include "sub.h"
#include "debug.h"
#include "string.h"
#include "ipc_type.h"

/*
        _1:LBA mode启用  0:chs(cyl header sector)启用
       |          _0:master(不妨称为第一块硬盘)被选定 1:slave(第二块硬盘)被选定
       |         |           __若lba为1,则为lba的24-27bit, 若lba为0则为header numer
      _|_       _|_  ________|_________
  1   lba   1   drv  hs3  hs2  hs1  hs0
  7    6    5    4    3    2    1    0

*/

#define MAKE_DEVIE_REG(lba, drv, hs) (((lba << 6) | (drv << 4) | (hs & 0xF)) | 0xA0) //lab drv: must be 0 or 1

#define ATA_REG_VAL_IDENTIFY    (0xEC)
#define ATA_READ        0x20
#define ATA_WRITE       0x30

#define ATA_DATA_REG            (0x1F0)
#define ATA_FEATURE_REG         (0x1F1)
#define ATA_SECTOR_COUNT_REG    (0x1F2)
#define ATA_LBA_LOW_REG         (0x1F3)
#define ATA_LBA_MID_REG         (0x1F4)
#define ATA_LBA_HIGH_REG        (0x1F5)
#define ATA_LBA_HIGH_REG        (0x1F5)
#define ATA_DEVICE_REG          (0x1F6)
#define ATA_COMMAND_REG         (0x1F7) //write
#define ATA_STATUS_REG          (0x1F7) //read
#define ATA_CONTROL_REG         (0x3F6)
#define STATUS_BUSY_MSK         (0x80) //1 << 7
#define STATUS_DATA_REQUEST     (0x08)
#define STATUS_NOT_BUSY_VAL     (0x00) //0 << 7

//#define DRV_OF_DEV(minor) minor <= MAX_PRIM ? minor / NR_PRIM_PER_DRIVE : (minor - HD1A_MINOR) / NR_SUB_PER_DRIVE
#define DRV_OF_DEV(minor) (minor <= 9 ?  minor / 5 : (minor - 16) / 64) //建议你参考下device register的drv位，因为我们这里想定要对应两块硬盘

typedef struct _hd_command_reg {
    u8 features;
    u8 sector_count;
    u8 lba_low;
    u8 lba_mid;
    u8 lba_high;
    u8 device;
    u8 command;
} HD_CMD_REG;


//# 分区表结构
//  偏移    长度    描述
//  0       1      状态(0x80:可引导 0x00:不可引导,其它非法)
//  1       1      起始磁头号
//  2       1      起始扇区号(仅用了低6位，高2位为起始柱面号的第8,9位)
//  3       1      起始柱面号低8位
//  4       1      分区类型
//  5       1      结束磁头号
//  6       1      结束扇区号(仅用了低6位，高2位为结束柱面号的第8，9位)
//  7       1      结束柱面号的低8位
//  8       4      起始扇区的LBA
//  12      4      扇区数目
typedef struct {
    unsigned char state;
    unsigned char start_header_no;
    unsigned char start_sect_no;
    unsigned char start_cyl_no;
    unsigned char part_type;
    unsigned char end_header_no;
    unsigned char end_sect_no;
    unsigned char end_cyl_no;
    unsigned int begin_lba;
    unsigned int sect_num;
} __attribute__((packed)) PART_DESC;

static char s_hd_buf[SECTOR_SIZE * 2] = {0};
static char s_hd_status = 0;

HD_INFO s_hd_info[1] = {0};

//0:wait ok in time_out ticks, -1:time out
static int wait_for_hd_status(const int msk,const int val, const int time_out) {
    int start = get_ticks();
    while((get_ticks() - start) < time_out) {
        u8 st = in_byte(ATA_STATUS_REG);
        if(val == (st & msk)) {
            return 0;
        }
    }
    return -1;
}

static int hd_cmd_out(HD_CMD_REG* cmd) {
    if(wait_for_hd_status(STATUS_BUSY_MSK, STATUS_NOT_BUSY_VAL, 100)) {
        assert(0);
        return -1;
    }

    out_byte(ATA_CONTROL_REG, 0); //enable ata interrupt

    out_byte(ATA_FEATURE_REG, cmd->features);
    out_byte(ATA_SECTOR_COUNT_REG, cmd->sector_count);
    out_byte(ATA_LBA_LOW_REG, cmd->lba_low);
    out_byte(ATA_LBA_MID_REG, cmd->lba_mid);
    out_byte(ATA_LBA_HIGH_REG, cmd->lba_high);
    out_byte(ATA_DEVICE_REG, cmd->device);
    out_byte(ATA_COMMAND_REG, cmd->command);
    return 0;
}

static void interrupt_wait() {
    MESSAGE msg;
    post(FN_RECV, INTERRUPT, &msg);
}

static void print_hd_id_info(u16* hd_inf) {

    int i, k;
    char s[64];

    struct iden_info_ascii {
        int idx;
        int len;
        char * desc;
    } iinfo[] = {{10, 20, "HD SN"}, /* Serial number in ASCII */
             {27, 40, "HD Model"} /* Model number in ASCII */ };

    for (k = 0; k < sizeof(iinfo)/sizeof(iinfo[0]); k++) {
        char * p = (char*)&hd_inf[iinfo[k].idx];
        for (i = 0; i < iinfo[k].len/2; i++) {
            s[i*2+1] = *p++;
            s[i*2] = *p++;
        }
        s[i*2] = 0;
        printf("%s: %s\n", iinfo[k].desc, s);
    }

    int capabilities = hd_inf[49];
    printf("LBA supported: %s\n",
           (capabilities & 0x0200) ? "Yes" : "No");

    int cmd_set_supported = hd_inf[83];
    printf("LBA48 supported: %s\n",
           (cmd_set_supported & 0x0400) ? "Yes" : "No");

    int sectors = ((int)hd_inf[61] << 16) + hd_inf[60];
    printf("HD size: %dMB\n", sectors * 512 / 1000000);
}

static void get_hd_id(int drive) {
    HD_CMD_REG cmd = {0};

    cmd.device = MAKE_DEVIE_REG(1, drive, 0);
    cmd.command = ATA_REG_VAL_IDENTIFY;
    hd_cmd_out(&cmd);
    interrupt_wait();
    port_read(ATA_DATA_REG, s_hd_buf, SECTOR_SIZE);
    print_hd_id_info((u16*)s_hd_buf);


    u16* hdinfo = (u16*)s_hd_buf;
    s_hd_info[drive].primary[0].start_sect = 0;
    /* Total Nr of User Addressable Sectors */
    s_hd_info[drive].primary[0].num_sect = ((int)hdinfo[61] << 16) + hdinfo[60];
}

int read_1block_data(int drive, int lba, char* buf) {
    //hd-hard ware treat lba as *= 512, to locate hard-address
    HD_CMD_REG cmd = {0};
    cmd.features = 0;
    cmd.sector_count = 1;
    cmd.lba_low = lba & 0xFF;
    cmd.lba_mid = (lba >> 8) & 0xFF;
    cmd.lba_high = (lba >> 16) & 0xFF;
    cmd.device = MAKE_DEVIE_REG(1, drive, (lba >> 24) & 0xF);

    cmd.command = ATA_READ;
    hd_cmd_out(&cmd);
    interrupt_wait();
    port_read(ATA_DATA_REG, buf, SECTOR_SIZE);
    return 0;
}

int read_data_from_hd(int drive, int lba, int offset, char* buf, int len) {
    char lbuf[512] = {0};
    read_1block_data(drive, lba, lbuf);
    memcpy(buf, lbuf + offset, len);
    return 0;
}

//set ext_part_base in stack, so no problem when more ext_part in 4 part_desc(than 1 classicly)
void part_read(int drv, int lba, int ext_part_base, int make_logic_idx) {
    char buf[16 * 4] = {0};
    int l2 = 0;
    read_data_from_hd(drv, lba, 0x1BE, buf, sizeof(buf));
    PART_DESC* p = (PART_DESC*)buf;
    for(int i = 0; i < 4; ++i) {
        if(p[i].part_type == 0) {
            continue;
        }

        if(0 == ext_part_base) {
            s_hd_info[drv].primary[i + 1].start_sect = lba + p[i].begin_lba;
            s_hd_info[drv].primary[i + 1].num_sect = p[i].sect_num;
        } else if(0x05 != p[i].part_type) {
            s_hd_info[drv].logical[make_logic_idx + i].start_sect = lba + p[i].begin_lba;
            s_hd_info[drv].logical[make_logic_idx + i].num_sect = p[i].sect_num;
            make_logic_idx++;
        }

        if(0 == ext_part_base || 0x05 != p[i].part_type) {
            printf("0x%X   0x%X       0x%X(%d)     0x%X(%d) %dM\n", p[i].state & 0xFF,
                    p[i].part_type & 0xFF, p[i].begin_lba + lba, lba + p[i].begin_lba, p[i].sect_num,
                    p[i].sect_num, p[i].sect_num * 512 / 1024 / 1024);
        }

        if(0x05 == p[i].part_type) {
            l2 = ext_part_base + p[i].begin_lba;
            if(0 == ext_part_base) {
                ext_part_base = p[i].begin_lba;
            }
            part_read(drv, l2, ext_part_base, make_logic_idx);
        }
    }
}

static void fdisk_list(int drv) {
    printf("state part_type begin_lba       sect_num\n");
    part_read(drv, 0, 0, 16); //NR_SUB_PER_PART
}

int read_hd_data(int drive, int lba, void* buf, int size) {
    HD_CMD_REG cmd = {0};
    cmd.device = MAKE_DEVIE_REG(1, drive, 0);
    cmd.command = ATA_REG_VAL_IDENTIFY;

    hd_cmd_out(&cmd);
    interrupt_wait();
    port_read(ATA_DATA_REG, buf, size);
    return 0;
}

void init_hd() {
    char* drv_num = (char*)(0x475); //get from bios data area
    printf("hd drive num:%d\n", (*drv_num) & 0xFF);
    assert(*drv_num);
    set_irq_handler(WIN_DISK_IRQ, hd_handler);
    enable_irq(WIN_DISK_IRQ);
    enable_irq(CASECADE_IRQ);
}

static void dev_open(int minor_no) {
    int drv = DRV_OF_DEV(minor_no);
    get_hd_id(drv);
    if(0 == s_hd_info[drv].open_cnt) {
        fdisk_list(drv);
    }
    s_hd_info[drv].open_cnt++;
}

static int hd_sect_rw(int rw_type, int minor, u32 rw_pos, int rw_sect_cnt, char* buf) {
    int drive = DRV_OF_DEV(minor);
    int rw_sect = rw_pos / 512;
    int logic_idx = (minor - HD1A_MINOR) % NR_SUB_PER_DRIVE;

    if(minor <= MAX_PRIM) {
        rw_sect += s_hd_info[drive].primary[minor].start_sect;
    } else {
        rw_sect += s_hd_info[drive].logical[logic_idx].start_sect;
    }

    HD_CMD_REG cmd = {0};
    cmd.features = 0;
    cmd.sector_count = rw_sect_cnt;
    cmd.lba_low = rw_sect & 0xFF;
    cmd.lba_mid = (rw_sect >> 8) & 0xFF;
    cmd.lba_high = (rw_sect >> 16) & 0xFF;
    cmd.device = MAKE_DEVIE_REG(1, drive, (rw_sect >> 24) & 0xF);
    if(MT_HD_READ == rw_type) {
        cmd.command = ATA_READ;
    } else {
        cmd.command = ATA_WRITE;
    }
    hd_cmd_out(&cmd);

    while(rw_sect_cnt > 0) {
        if(MT_HD_READ == rw_type) {
            interrupt_wait();
            port_read(ATA_DATA_REG, buf, SECTOR_SIZE);
        } else {
            if(wait_for_hd_status(STATUS_DATA_REQUEST, STATUS_DATA_REQUEST, 100)) {
                assert(0);
                return -1;
            }
            port_write(ATA_DATA_REG, buf, SECTOR_SIZE);
            interrupt_wait();
        }
        buf += 512;
        rw_sect_cnt--;
    }

    return 0;
}
static void hd_rw(int mt, MT_HD_RW_TYPE* p) {
    assert(0 == p->rw_len % 512);
    assert(0 == p->pos % 512);
    hd_sect_rw(mt, p->minor, p->pos, p->rw_len / 512, p->buf);
}
static void hd_io_ctrl(MT_HD_IO_CTRL_TYPE* hd_io) {
    int drive = DRV_OF_DEV(hd_io->minor);
    if(hd_io->minor <= MAX_PRIM) {
        memcpy(&hd_io->pi, &s_hd_info[drive].primary[hd_io->minor], sizeof(PART_INFO));
    } else {
        int logic_idx = (hd_io->minor - HD1A_MINOR) % NR_SUB_PER_DRIVE;
        memcpy(&hd_io->pi, &s_hd_info[drive].logical[logic_idx], sizeof(PART_INFO));
    }
}

void task_hd() {
    MESSAGE msg;
    init_hd();
    while(1) {
        post(FN_RECV, TASK_ANY, &msg);
        switch(msg.type) {
        case MT_DEV_OPEN:
            dev_open(CPT(MT_DEV_OPEN_TYPE*, msg.data)->minor);
            break;
        case MT_HD_IO_CTRL:
            hd_io_ctrl((MT_HD_IO_CTRL_TYPE*)&msg.data);
            break;
        case MT_HD_READ:
        case MT_HD_WRITE:
            hd_rw(msg.type, (MT_HD_RW_TYPE*)&msg.data);
            break;
        case MT_FDISK_LIST:
            fdisk_list(0);
            break;
        default:
            break;
        }
        post(FN_SEND, msg.req_pid, &msg);
    }
}

void hd_handler(int irq_no) {
    s_hd_status = in_byte(ATA_STATUS_REG);
    notify_int(TASK_IDX_HD_DRV);
}


